package worklog;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.TreeMap;

import worklog.dto.HolidayDTO;

import com.google.gson.Gson;

public class Holidays {
	private int year;
	private ArrayList<HolidayDTO> days;
	private static DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
	
	// Constructors
	public Holidays(){ this(Calendar.getInstance().get(Calendar.YEAR)); }
	public Holidays(int year){ 
		this.year = year;
		setAll();
	}
	public Holidays(String date){
		String yS = date.substring(0, 4);
		int y = Integer.parseInt(yS);
		this.year = y;
		setAll();
	}
	
	
	
	/*** OUTPUT ************************************************************************************************/
	
	@Override
	public String toString(){ return new Gson().toJson(this); }
	
	/**
	 * Short json: { "date": "holiday name", ... }
	 */
	public String toJson(){ return new Gson().toJson(new TreeMap<String, String>(toMap())); }
	
	public HashMap<String, String> toMap(){
		HashMap<String,String> map = new HashMap<String, String>();
		for(HolidayDTO day : days) map.put(day.getDate(), day.getName());		// convert this to "date":"name" json
		return map;
	}
	
	public String isHoliday(String date){
		HashMap<String,String> map = toMap();
		if (map.containsKey(date)) return map.get(date);
		return null;
	}
	
	private void setAll(){
		days = new ArrayList<HolidayDTO>();
		days.add(newYear());
		days.add(stPatricks());
		days.add(goodFriday());
		days.add(easterSunday());
		days.add(easterMonday());
		days.add(bhMay());
		days.add(bhJun());
		days.add(bhAug());
		days.add(bhOct());
		days.add(xmasEve());
		days.add(xmas());
		days.add(stStephens());
		days.add(newYearsEve());
	}
	/*** OUTPUT ************************************************************************************************/
	
	
	
	/*** GETTERS ***********************************************************************************************/
	// bank holiday - May 
	private HolidayDTO bhMay() { return getFirstMonday("Bank Holiday", year, 5); }
	
	// bank holiday - June 
	private HolidayDTO bhJun() { return getFirstMonday("Bank Holiday", year, 6); }
	
	// bank holiday - August 
	private HolidayDTO bhAug() { return getFirstMonday("Bank Holiday", year, 8); }
	
	// bank holiday - October
	private HolidayDTO bhOct() { return getLastMonday("Bank Holiday", year, 10); }

	// New Year
	private HolidayDTO newYear() { return getStaticDay("New Year", year, 1, 1); }

	// St. Patrick's Day - 17/03
	private HolidayDTO stPatricks() { return getStaticDay("St. Patrick's Day", year, 3, 17); }
	
	// Christmas Day - 24/12
	private HolidayDTO xmasEve() { return getStaticDay("Christmas Eve", year, 12, 24); }
	
	// Christmas Day - 25/12
	private HolidayDTO xmas() { return getStaticDay("Christmas Day", year, 12, 25); }
	
	// St. Stephen's Day - 26/12
	private HolidayDTO stStephens() { return getStaticDay("St. Stephen's Day", year, 12, 26); }
	
	// New Year's Eve
	private HolidayDTO newYearsEve() { return getStaticDay("New Year's Eve", year, 12, 31); }

	// Good Friday
	private HolidayDTO goodFriday() { 
		HolidayDTO day = new HolidayDTO();
		Calendar cal = EasterMonday(year);
    	cal.add(Calendar.DAY_OF_MONTH, -3);
		day.setDate(formatter.format(cal.getTime()));
		day.setName("Good Friday");
		day.setWeekday("Friday");
		return day;
	}
	
	// Easter Sunday
	private HolidayDTO easterSunday() { 
		HolidayDTO day = new HolidayDTO();
    	Calendar cal = EasterMonday(year);
    	cal.add(Calendar.DAY_OF_MONTH, -1);
    	day.setDate(formatter.format(cal.getTime()));
    	day.setName("Easter");
    	day.setWeekday("Sunday");
    	return day;
	}
	
	// Easter Monday
	private HolidayDTO easterMonday() { 
		HolidayDTO day = new HolidayDTO();
		Calendar cal = EasterMonday(year);
		day.setDate(formatter.format(cal.getTime()));
		day.setName("Easter Monday");
		day.setWeekday("Monday");
		return day;
	}
	/*** GETTERS ***********************************************************************************************/
	
	
	
	
	

	/*** HELPER FUNCTIONS **************************************************************************************/
	
	private HolidayDTO getFirstMonday(String name, int y, int m){
    	Calendar cal = Calendar.getInstance();
		cal.set(y, m-1, 1);
		cal.set(Calendar.DAY_OF_WEEK,Calendar.MONDAY);
		cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 1);
    	
		HolidayDTO day = new HolidayDTO();
    	day.setDate(formatter.format(cal.getTime()));
    	day.setName(name);
    	day.setWeekday("Monday");
    	return day;
	}
	
	private HolidayDTO getLastMonday(String name, int y, int m){
		Calendar cal = Calendar.getInstance();
		cal.set(y, m-1, 1);
		cal.set(Calendar.DAY_OF_WEEK,Calendar.MONDAY);
		cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1);
		
		HolidayDTO day = new HolidayDTO();
		day.setDate(formatter.format(cal.getTime()));
		day.setName(name);
		day.setWeekday("Monday");
		return day;
	}
	
	
	/**
	 * Get the HolidayDTO object from a given date and name
	 * @param name		name of a holiday
	 * @param y			year
	 * @param m			month (1-12)
	 * @param d			day
	 * @return			HolidayDTO object with the correct date
	 */
	private HolidayDTO getStaticDay(String name, int y, int m, int d){
		HolidayDTO day = new HolidayDTO();
    	Calendar cal = Calendar.getInstance();
    	//cal.set(y, m-1, getDay(y, m-1, d));
    	cal.set(y, m-1, d);
    	day.setDate(formatter.format(cal.getTime()));
    	day.setName(name);
    	day.setWeekday(cal.get(Calendar.DAY_OF_WEEK));
    	return day;
	}
	
	
	
	
	/**
	 * Calculate Easter Monday
	 * 
	 * Written by Gregory N. Mirsky
	 * 
	 * Source: 2nd Edition by Peter Duffett-Smith. It was originally from
	 * Butcher's Ecclesiastical Calendar, published in 1876. This algorithm
	 * has also been published in the 1922 book General Astronomy by Spencer
	 * Jones; in The Journal of the British Astronomical Association
	 * (Vol.88, page 91, December 1977); and in Astronomical Algorithms
	 * (1991) by Jean Meeus.
	 * 
	 * This algorithm holds for any year in the Gregorian Calendar, which
	 * (of course) means years including and after 1583.
	 * 
	 * a=year%19 b=year/100 c=year%100 d=b/4 e=b%4 f=(b+8)/25 g=(b-f+1)/3
	 * h=(19*a+b-d-g+15)%30 i=c/4 k=c%4 l=(32+2*e+2*i-h-k)%7
	 * m=(a+11*h+22*l)/451 Easter Month =(h+l-7*m+114)/31 [3=March, 4=April]
	 * p=(h+l-7*m+114)%31 Easter Date=p+1 (date in Easter Month)
	 * 
	 * Note: Integer truncation is already factored into the calculations.
	 * Using higher percision variables will cause inaccurate calculations.
	 */
	private Calendar EasterMonday(int nYear) {
		int nA = nYear % 19;
		int nB = nYear / 100;
		int nC = nYear % 100;
		int nD = nB / 4;
		int nE = nB % 4;
		int nF = (nB + 8) / 25;
		int nG = (nB - nF + 1) / 3;
		int nH = (19 * nA + nB - nD - nG + 15) % 30;
		int nI = nC / 4;
		int nK = nC % 4;
		int nL = (32 + 2 * nE + 2 * nI - nH - nK) % 7;
		int nM = (nA + 11 * nH + 22 * nL) / 451;
		int nP = (nH + nL - 7 * nM + 114) % 31;
		int nEasterMonth = (nH + nL - 7 * nM + 114) / 31;		// [3=March, 4=April]
		int nEasterDay = nP + 2;								// Date in Easter Month.
		Calendar cal = Calendar.getInstance();
		cal.set(nYear, nEasterMonth-1, nEasterDay);
		return cal;
	}
	
	/*** HELPER FUNCTIONS **************************************************************************************/
	
	
	
}
